/**
 * $Id: SharedGraphModel.java,v 1.3 2012-01-13 12:35:04 david Exp $
 * Copyright (c) 2007-2012, JGraph Ltd
 */
package com.kg.rails.component.editor.sharing;

import com.kg.rails.component.editor.io.Codec;
import com.kg.rails.component.editor.model.GraphModel;
import com.kg.rails.component.editor.model.GraphModel.mxChildChange;
import com.kg.rails.component.editor.model.ICell;
import com.kg.rails.component.editor.model.IGraphModel.mxAtomicGraphModelChange;
import com.kg.rails.component.editor.util.EditorEvent;
import com.kg.rails.component.editor.util.EditorEventObject;
import com.kg.rails.component.editor.util.EditorUndoableEdit;
import com.kg.rails.component.editor.util.XmlUtils;
import org.w3c.dom.Node;

import java.util.LinkedList;

/**
 * Implements a diagram that may be shared among multiple sessions.
 */
public class SharedGraphModel extends SharedState {

    /**
     *
     */
    protected GraphModel model;

    /**
     *
     */
    protected Codec codec = new Codec() {
        public Object lookup(String id) {
            return model.getCell(id);
        }
    };

    /**
     * Whether remote changes should be significant in the
     * local command history. Default is true.
     */
    protected boolean significantRemoteChanges = true;

    /**
     * Constructs a new diagram with the given model.
     *
     * @param model Initial model of the diagram.
     */
    public SharedGraphModel(GraphModel model) {
        super(null); // Overrides getState
        this.model = model;
    }

    /**
     * @return the model
     */
    public GraphModel getModel() {
        return model;
    }

    /**
     * @return the significantRemoteChanges
     */
    public boolean isSignificantRemoteChanges() {
        return significantRemoteChanges;
    }

    /**
     * @param significantRemoteChanges the significantRemoteChanges to set
     */
    public void setSignificantRemoteChanges(boolean significantRemoteChanges) {
        this.significantRemoteChanges = significantRemoteChanges;
    }

    /**
     * Returns the initial state of the diagram.
     */
    public String getState() {
        return XmlUtils.getXml(codec.encode(model));
    }

    /**
     *
     */
    public synchronized void addDelta(String edits) {
        // Edits are not added to the history. They are sent straight out to
        // all sessions and the model is updated so the next session will get
        // these edits via the new state of the model in getState.
    }

    /**
     *
     */
    protected String processEdit(Node node) {
        mxAtomicGraphModelChange[] changes = decodeChanges(node.getFirstChild());

        if (changes.length > 0) {
            EditorUndoableEdit edit = createUndoableEdit(changes);

            // No notify event here to avoid the edit from being encoded and transmitted
            // LATER: Remove changes property (deprecated)
            model.fireEvent(new EditorEventObject(EditorEvent.CHANGE, "edit", edit,
                    "changes", changes));
            model.fireEvent(new EditorEventObject(EditorEvent.UNDO, "edit", edit));
            fireEvent(new EditorEventObject(EditorEvent.FIRED, "edit", edit));
        }

        return super.processEdit(node);
    }

    /**
     * Creates a new EditorUndoableEdit that implements the notify function to fire
     * a change and notify event via the model.
     */
    protected EditorUndoableEdit createUndoableEdit(
            mxAtomicGraphModelChange[] changes) {
        EditorUndoableEdit edit = new EditorUndoableEdit(this, significantRemoteChanges) {
            public void dispatch() {
                // LATER: Remove changes property (deprecated)
                ((GraphModel) source).fireEvent(new EditorEventObject(
                        EditorEvent.CHANGE, "edit", this, "changes", changes));
                ((GraphModel) source).fireEvent(new EditorEventObject(
                        EditorEvent.NOTIFY, "edit", this, "changes", changes));
            }
        };

        for (int i = 0; i < changes.length; i++) {
            edit.add(changes[i]);
        }

        return edit;
    }

    /**
     * Adds removed cells to the codec object lookup for references to the removed
     * cells after this point in time.
     */
    protected mxAtomicGraphModelChange[] decodeChanges(Node node) {
        // Updates the document in the existing codec
        codec.setDocument(node.getOwnerDocument());

        LinkedList<mxAtomicGraphModelChange> changes = new LinkedList<mxAtomicGraphModelChange>();

        while (node != null) {
            Object change;

            if (node.getNodeName().equals("mxRootChange")) {
                // Handles the special case were no ids should be
                // resolved in the existing model. This change will
                // replace all registered ids and cells from the
                // model and insert a new cell hierarchy instead.
                Codec tmp = new Codec(node.getOwnerDocument());
                change = tmp.decode(node);
            } else {
                change = codec.decode(node);
            }

            if (change instanceof mxAtomicGraphModelChange) {
                mxAtomicGraphModelChange ac = (mxAtomicGraphModelChange) change;

                ac.setModel(model);
                ac.execute();

                // Workaround for references not being resolved if cells have
                // been removed from the model prior to being referenced. This
                // adds removed cells in the codec object lookup table.
                if (ac instanceof mxChildChange
                        && ((mxChildChange) ac).getParent() == null) {
                    cellRemoved(((mxChildChange) ac).getChild());
                }

                changes.add(ac);
            }

            node = node.getNextSibling();
        }

        return changes.toArray(new mxAtomicGraphModelChange[changes.size()]);
    }

    /**
     * Adds removed cells to the codec object lookup for references to the removed
     * cells after this point in time.
     */
    public void cellRemoved(Object cell) {
        codec.putObject(((ICell) cell).getId(), cell);

        int childCount = model.getChildCount(cell);

        for (int i = 0; i < childCount; i++) {
            cellRemoved(model.getChildAt(cell, i));
        }
    }

}
